`timescale 1ns / 1ps
/*
 v1: based on arp_tx date: 2024/3/26 08:40:23
*/
module udp_tx #(
    parameter 
            BOARD_MAC = 48'h00_11_22_33_44_55,
            BOARD_IP  = {8'd192,8'd168,8'd1,8'd10},
            BOARD_UDP_PORT_NUM = 16'd1234,
            DES_UDP_PORT_NUM = 16'd1234 // 默认的
)(
    input               clk             ,
    input               rst_n           ,
    input       [47:0]  des_mac         ,   // 从rx模块缓存着的目的mac
    input       [31:0]  des_ip          ,
    input       [31:0]  crc_data        ,
    input       [7:0]   crc_next        ,
    input               tx_start        ,
    input       [7:0]   udp_fifo_rd_data,   // read data from fifo
    input       [15:0]  udp_des_port_num,
    input       [15:0]  len_udp_data    ,
    input       [31:0]  udp_tx_data_sum ,
    input       [15:0]  rd_data_count   ,
    output reg          gmii_tx_en      ,   // gmii发信号使能
    output reg          udp_fifo_rd_en  ,   // fifo read enable
    output reg  [7:0]   gmii_tx_data    ,   // gmii发数据内容
    output reg          tx_done         ,
    output reg          udp_tx_busy     ,
    output reg          crc_clr         ,
    output reg          crc_en              
);

localparam 
            IDLE     = 4'd0, 
            PRE      = 4'd1,
            SFD      = 4'd2,
            ETH_HEAD = 4'd3,
            IP_HEAD  = 4'd4,
            UDP_HEAD = 4'd5,
            DATA     = 4'd6,
            FCS      = 4'd7,
            CHECK    = 4'd8;
localparam
            ETH_TYPE        = 16'h0800, // 表示IP协议
            UDP_PROTOCOL    = 8'd17;    // 表示UDP协议

// reg define
reg [3:0]   cur_state, next_state;
reg         skip_en;
reg [10:0]  cnt;
reg [ 7:0]  eth_head[13:0]; // 以太网帧头
reg [ 7:0]  ip_head[19:0];  // ip首部
reg [ 7:0]  udp_head[ 7:0]; // udp首部
reg [31:0]  ip_head_check_sum_t1;
reg [31:0]  ip_head_check_sum_t2;
reg [31:0]  udp_head_check_sum_t1;
reg [31:0]  udp_head_check_sum_t2;
reg [15:0]  cnt_fifo_rd_data_len;
reg [15:0]  len_udp_data_r;
/****************************************
                main code
****************************************/     

// 状态转移
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)
        cur_state <= IDLE;
    else
        cur_state <= next_state;
end

// 状态转移条件判断
always @(*)begin
    next_state = IDLE;
    case (cur_state)
        IDLE: begin
            if(skip_en)
                next_state = CHECK;
            else
                next_state = IDLE; 
        end
        CHECK: begin
            if(skip_en)
                next_state = PRE;
            else
                next_state = CHECK;
        end
        PRE: begin
            if(skip_en)
                next_state = SFD;
            else
                next_state = PRE; 
        end
        SFD: begin
            if(skip_en)
                next_state = ETH_HEAD;
            else
                next_state = SFD; 
        end
        ETH_HEAD:begin
            if(skip_en)
                next_state = IP_HEAD;
            else
                next_state = ETH_HEAD; 
        end
        IP_HEAD:begin
            if(skip_en)
                next_state = UDP_HEAD;
            else
                next_state = IP_HEAD; 
        end
        UDP_HEAD:begin
            if(skip_en)
                next_state = DATA;
            else
                next_state = UDP_HEAD; 
        end
        DATA: begin
            if(skip_en)
                next_state = FCS;
            else
                next_state = DATA; 
        end
        FCS: begin
            if(skip_en)
                next_state = IDLE;
            else
                next_state = FCS; 
        end
    endcase
end

// 状态输出
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        skip_en         <= 1'd0;
        udp_fifo_rd_en  <= 1'd0;
        gmii_tx_en      <= 1'd0;
        crc_clr         <= 1'd0;
        crc_en          <= 1'd0;
        udp_tx_busy     <= 1'd0;
        tx_done         <= 1'd0;
        cnt             <= 11'd0;
        gmii_tx_data    <= 8'd0;
        len_udp_data_r <= 16'd0;

        //ethernet head frame  
        {eth_head[0],eth_head[1],eth_head[2],eth_head[3],eth_head[4],eth_head[5]} <= 48'd0; // destination mac

        eth_head[6] <= BOARD_MAC[47:40];        // source mac, is may board mac.
        eth_head[7] <= BOARD_MAC[39:32];    
        eth_head[8] <= BOARD_MAC[31:24];    
        eth_head[9] <= BOARD_MAC[23:16];    
        eth_head[10] <= BOARD_MAC[15:8];    
        eth_head[11] <= BOARD_MAC[7:0];     
        eth_head[12] <= ETH_TYPE[15:8];         // ethernet type(Ip protocol)
        eth_head[13] <= ETH_TYPE[7:0];  

        //IP head data                           
        ip_head[0] <= 8'h45;
        ip_head[1] <= 8'd00;
        {ip_head[2],ip_head[3]} <= 16'd60;      // total length
        {ip_head[4],ip_head[5]} <= 16'd0;       // flag, when udp sends once data, flag add one number.
        {ip_head[6],ip_head[7]} <= 16'h0000;    // sign and offset
        ip_head[8] <= 8'h80;                    // frame live time
        ip_head[9] <= UDP_PROTOCOL;             // udp type code
        {ip_head[10],ip_head[11]} <= 16'd0;     // IP check sum
        {ip_head[12],ip_head[13],ip_head[14],ip_head[15]} <= BOARD_IP;
        {ip_head[16],ip_head[17],ip_head[18],ip_head[19]} <= 32'd0;

        
        {udp_head[2], udp_head[3]} <= DES_UDP_PORT_NUM;     // destination port
        {udp_head[4], udp_head[5]} <= 16'd0;    // udp length
        {udp_head[6], udp_head[7]} <= 16'd0;                // udp check sum
        cnt_fifo_rd_data_len <= 16'd0;
    end
    else begin
        skip_en <= 1'd0;
        tx_done <= 1'd0;
        crc_clr <= 1'd0;
        case (next_state)
            IDLE:begin
                gmii_tx_data <= 8'd0;
                udp_head_check_sum_t1 <= 32'd0;
                udp_head_check_sum_t2 <= 32'd0;
                ip_head_check_sum_t1 <= 32'd0;
                ip_head_check_sum_t2 <= 32'd0;

                if(tx_start)begin
                    skip_en <= 1'd1;
                    len_udp_data_r <= len_udp_data;
                    {udp_head[0], udp_head[1]} <= 16'd1234;   // source port

                    {eth_head[0],eth_head[1],eth_head[2],eth_head[3],eth_head[4],eth_head[5]} <= des_mac; // destination mac.
                    {ip_head[16],ip_head[17],ip_head[18],ip_head[19]} <= des_ip;// destination ip.
                    
                    {ip_head[2],ip_head[3]} <= 16'd28 + len_udp_data;
                    {udp_head[4],udp_head[5]} <= 16'd8 + len_udp_data; // udp length
                    {ip_head[4],ip_head[5]} <= {ip_head[4],ip_head[5]} + 16'd1; // 累加加1
                    // if udp rx send destination ip and mac here, udp will send data to this address.
                    if(udp_des_port_num != 16'd0)
                        {udp_head[2],udp_head[3]} <= udp_des_port_num;
                end
            end
            CHECK:begin
                cnt <= cnt + 11'd1;
                case (cnt)
                    // ip head check sum
                    11'd0: ip_head_check_sum_t1 <= {ip_head[0],ip_head[1]} + {ip_head[2],ip_head[3]};
                    11'd1: begin
                        ip_head_check_sum_t1 <= {ip_head[4],ip_head[5]} + {ip_head[6],ip_head[7]};
                        ip_head_check_sum_t2 <= ip_head_check_sum_t2 + ip_head_check_sum_t1;
                    end
                    11'd2: begin
                        ip_head_check_sum_t1 <= {ip_head[8],ip_head[9]} + {ip_head[12],ip_head[13]};
                        ip_head_check_sum_t2 <= ip_head_check_sum_t2 + ip_head_check_sum_t1;
                    end
                    11'd3: begin
                        ip_head_check_sum_t1 <= {ip_head[14],ip_head[15]} + {ip_head[16],ip_head[17]};
                        ip_head_check_sum_t2 <= ip_head_check_sum_t2 + ip_head_check_sum_t1;
                    end
                    11'd4: ip_head_check_sum_t2 <= ip_head_check_sum_t2 + ip_head_check_sum_t1;
                    11'd5: ip_head_check_sum_t2 <= ip_head_check_sum_t2 + {ip_head[18],ip_head[19]};
                    11'd6: if(ip_head_check_sum_t2[31:16] != 16'd0)
                            ip_head_check_sum_t1 <= ip_head_check_sum_t2[31:16] + ip_head_check_sum_t2[15:0];
                        else
                            {ip_head[10], ip_head[11]} <= ~ip_head_check_sum_t2[15:0];
                    11'd7: if(ip_head_check_sum_t1[31:16] != 16'd0)
                            {ip_head[10], ip_head[11]} <= ~{ip_head_check_sum_t1[31:16] +ip_head_check_sum_t1[15:0]};
                        else  // 写if 记得写 else 真的会出大问题！！！！
                            {ip_head[10], ip_head[11]} <= ~ip_head_check_sum_t1[15:0]; 

                    // udp head check sum
                    11'd8:udp_head_check_sum_t1 <= BOARD_IP[31:16] + BOARD_IP[15:0];
                    11'd9:begin
                        udp_head_check_sum_t1 <= des_ip[31:16] + des_ip[15:0];
                        udp_head_check_sum_t2 <= udp_head_check_sum_t2 + udp_head_check_sum_t1;
                    end
                    11'd10:begin
                        udp_head_check_sum_t1 <= UDP_PROTOCOL + {udp_head[4], udp_head[5]};// 伪首部结束
                        udp_head_check_sum_t2 <= udp_head_check_sum_t2 + udp_head_check_sum_t1;
                    end
                    11'd11:begin
                        udp_head_check_sum_t1 <= {udp_head[0],udp_head[1]} + {udp_head[2],udp_head[3]};
                        udp_head_check_sum_t2 <= udp_head_check_sum_t2 + udp_head_check_sum_t1;
                    end
                    11'd12:begin
                        udp_head_check_sum_t1 <= {udp_head[4],udp_head[5]} + udp_tx_data_sum;// udp 首部和数据结束 
                        udp_head_check_sum_t2 <= udp_head_check_sum_t2 + udp_head_check_sum_t1;
                    end
                    11'd13:udp_head_check_sum_t2 <= udp_head_check_sum_t2 + udp_head_check_sum_t1;
                    11'd14:if(udp_head_check_sum_t2[31:16] != 16'd0)
                            udp_head_check_sum_t1 <= udp_head_check_sum_t2[31:16] +udp_head_check_sum_t2[15:0];
                        else
                            {udp_head[6], udp_head[7]} <= ~udp_head_check_sum_t2[31:16];
                    11'd15:begin
                        udp_tx_busy <= 1'd1;
                        gmii_tx_en <= 1'd1;
                        skip_en <= 1'd1;
                        cnt <= 11'd0;
                        {udp_head[6], udp_head[7]} <= 16'h0000; // is always 0
                        // if(udp_head_check_sum_t1 != 31'd0)
                        //     if(udp_head_check_sum_t1[31:16] != 16'd0)
                        //         {udp_head[6], udp_head[7]} <= ~(udp_head_check_sum_t1[31:16] + udp_head_check_sum_t1[15:0]);
                        //     else
                        //         {udp_head[6], udp_head[7]} <= ~udp_head_check_sum_t1[15:0]; // 漏写了，导致udp_head[6], udp_head[7]一直为0
                        // else ;
                    end
                endcase
            end
            PRE:begin
                cnt <= cnt + 11'd1;
                gmii_tx_data <= 8'h55;
                if(cnt == 11'd6)begin
                    cnt <= 11'd0;
                    skip_en <= 1'd1;
                end
            end
            SFD:begin
                gmii_tx_data <= 8'hd5;
                skip_en <= 1'd1;
            end 
            ETH_HEAD:begin
                cnt <= cnt + 11'd1;
                case (cnt)
                    11'd0:begin
                        crc_en <= 1'd1;
                        gmii_tx_data <= eth_head[0];  
                    end
                    11'd1:gmii_tx_data <= eth_head[1];  
                    11'd2:gmii_tx_data <= eth_head[2];  
                    11'd3:gmii_tx_data <= eth_head[3];  
                    11'd4:gmii_tx_data <= eth_head[4];  
                    11'd5:gmii_tx_data <= eth_head[5];

                    11'd6:gmii_tx_data <= eth_head[6];  
                    11'd7:gmii_tx_data <= eth_head[7];  
                    11'd8:gmii_tx_data <= eth_head[8];  
                    11'd9:gmii_tx_data <= eth_head[9];  
                    11'd10:gmii_tx_data <= eth_head[10];  
                    11'd11:gmii_tx_data <= eth_head[11];

                    11'd12:gmii_tx_data <= eth_head[12];  
                    11'd13:begin
                        gmii_tx_data <= eth_head[13]; 
                        cnt <= 11'd0;
                        skip_en <= 1'd1;
                    end 
                endcase
            end
            IP_HEAD:begin
                cnt <= cnt + 11'd1;
                case (cnt)
                    11'd0: gmii_tx_data <= ip_head[0];
                    11'd1: gmii_tx_data <= ip_head[1];
                    11'd2: gmii_tx_data <= ip_head[2];
                    11'd3: gmii_tx_data <= ip_head[3];
                    11'd4: gmii_tx_data <= ip_head[4];
                    11'd5: gmii_tx_data <= ip_head[5];
                    11'd6: gmii_tx_data <= ip_head[6];
                    11'd7: gmii_tx_data <= ip_head[7];
                    11'd8: gmii_tx_data <= ip_head[8];            
                    11'd9: gmii_tx_data <= ip_head[9];
                    11'd10: gmii_tx_data <= ip_head[10];// ip_head[10] ip_head[11] check sum
                    11'd11: gmii_tx_data <= ip_head[11];
                    11'd12: gmii_tx_data <= ip_head[12];
                    11'd13: gmii_tx_data <= ip_head[13];
                    11'd14: gmii_tx_data <= ip_head[14];
                    11'd15: gmii_tx_data <= ip_head[15];
                    11'd16: gmii_tx_data <= ip_head[16];
                    11'd17: gmii_tx_data <= ip_head[17];
                    11'd18: gmii_tx_data <= ip_head[18];
                    11'd19: begin
                        gmii_tx_data <= ip_head[19];
                        cnt <= 11'd0;
                        skip_en <= 1'd1;
                    end
                endcase
            end
            UDP_HEAD:begin
                cnt <= cnt + 11'd1;
                case (cnt)
                    11'd0: gmii_tx_data <= udp_head[0];// 源端口
                    11'd1: gmii_tx_data <= udp_head[1];
                    11'd2: gmii_tx_data <= udp_head[2];// 目标端口
                    11'd3: begin
                        gmii_tx_data <= udp_head[3];// udp_head[6] udp_head[7] 是udp的首部校验和，由伪首部、upd首部和udp数据构成
                        // udp_head_check_sum_t1 <= BOARD_IP[31:16] + BOARD_IP[15:0] + des_ip[31:16] + des_ip[15:0] + UDP_PROTOCOL + {udp_head[4], udp_head[5]}// 伪首部结束
                        //                        + {udp_head[0],udp_head[1]} + {udp_head[2],udp_head[3]} + {udp_head[4],udp_head[5]} // udp 首部结束
                        //                        + udp_tx_data_sum;  // 危险代码，可能不满足时序
                    end
                    11'd4: gmii_tx_data <= udp_head[4];
                    11'd5: gmii_tx_data <= udp_head[5];
                    11'd6: begin
                        udp_fifo_rd_en <= 1'd1;  // 提前两个时钟打开，一个时钟给到fifo的读迟滞，一个时钟给到udp的赋值迟滞
                        gmii_tx_data <= udp_head[6];
                    end
                    11'd7: begin
                        gmii_tx_data <= udp_head[7];
                        cnt <= 11'd0;
                        skip_en <= 1'd1; 
                    end
                endcase
            end
            DATA:begin// 如果数据长度大于18，则直接发送，否则发送时用0补全缺少的byte
                cnt <= cnt + 11'd1;
                if(len_udp_data_r > 16'd18)begin
                    if(cnt < len_udp_data_r)begin
                        gmii_tx_data <= udp_fifo_rd_data;
                        cnt_fifo_rd_data_len <= cnt_fifo_rd_data_len + 16'd1;
                    end
                    else ;
                    if(cnt == len_udp_data_r -16'd1)begin
                        cnt_fifo_rd_data_len <= 16'd0;
                        skip_en <= 1'd1;
                        cnt <= 11'd0;
                    end
                    else ;
                    if(cnt == len_udp_data_r -16'd2)
                        udp_fifo_rd_en <= 1'd0;
                    else ;
                end
                else begin
                    if(cnt < len_udp_data_r)
                        gmii_tx_data <= udp_fifo_rd_data;
                    else begin
                        gmii_tx_data <= 8'd0;
                        udp_fifo_rd_en <= 1'd0;
                        if(cnt == 11'd17)begin
                            cnt <= 11'd0;
                            skip_en <= 1'd1;
                        end
                    end
                end
            end
            FCS:begin
                cnt <= cnt + 7'd1;
                case (cnt)
                    7'd0: begin
                        crc_en <= 1'd0;
                        gmii_tx_data <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
                                 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
                    end
                    7'd1: gmii_tx_data <= {~crc_data[16], ~crc_data[17], ~crc_data[18],
                                 ~crc_data[19], ~crc_data[20], ~crc_data[21], 
                                 ~crc_data[22],~crc_data[23]};  
                    7'd2: gmii_tx_data <= {~crc_data[8], ~crc_data[9], ~crc_data[10],
                                 ~crc_data[11],~crc_data[12], ~crc_data[13], 
                                 ~crc_data[14],~crc_data[15]}; 
                    7'd3: begin
                        gmii_tx_data <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
                                 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
                    end
                    7'd4:begin
                        gmii_tx_data <= 8'd0;
                        skip_en <= 1'd1;
                        tx_done <= 1'd1;
                        udp_tx_busy <= 1'd0;
                        cnt <= 11'd0;
                        crc_clr <= 1'd1;
                        gmii_tx_en <= 1'd0;
                    end  
                endcase
            end      
        endcase
    end
end

endmodule